package fr.generali.accueilclient.gwt.client.state;

import com.google.gwt.core.client.Callback;
import com.google.web.bindery.autobean.shared.AutoBean;
import com.google.web.bindery.autobean.shared.AutoBeanCodex;
import com.google.web.bindery.autobean.shared.AutoBeanFactory;
import com.google.web.bindery.autobean.shared.AutoBeanUtils;

import fr.generali.accueilclient.gwt.client.state.store.HashMapStateStore;

/**
 * StateManager est l'élément central de l'API State. Il permet de mapper les
 * objets des états vers des strings Json et l'inverse puis les envoyer vers un
 * {@link StateStore}. Le mapping JSON-Objet se fait à l'aide de l'API de GWT
 * Autobean.
 */
public class StateManager {

    /**
     * The state bean factory responsible for creating and decoding the state
     * beans. La Bean Factory (Autobean) permettant de créer et de décoder les
     * états.
     */
    private final AutoBeanFactory stateBeanFactory;

    private final StateStore stateStore;

    /**
     * StateManager initialisé par défaut avec un HashMapProvider.
     */
    public StateManager(AutoBeanFactory stateBeanFactory) {
        this.stateBeanFactory = stateBeanFactory;
        this.stateStore = new HashMapStateStore();
    }

    public StateManager(StateStore stateProvider, AutoBeanFactory stateBeanFactory) {
        this.stateBeanFactory = stateBeanFactory;
        this.stateStore = stateProvider;
    }

    /**
     * Clears the state bean.
     */
    public void clear(String stateId) {
        stateStore.clear(stateId);
    }

    /**
     * Retourne le bean state. On utilise un callback pour supporter les appels
     * RPC.
     */
    public <T> void get(String stateId, final Class<T> stateBeanType, final Callback<T, Throwable> callback) {

        // Récupération de l'état à partir du stateStore.
        stateStore.get(stateId, new Callback<String, Throwable>() {
            @Override
            public void onSuccess(String state) {

                AutoBean<T> stateBean = null;

                if (state == null || state.length() == 0) {

                    // Si le stateStore ne retourne rien fournir une instance par défaut.
                    stateBean = stateBeanFactory.create(stateBeanType);

                    assert stateBean != null : "Le type " + stateBeanType
                                    + " n'est pas déclaré dans la StateBeanFactory";

                } else {
                    // Sinon décoder le String en Objet
                    stateBean = AutoBeanCodex.decode(stateBeanFactory, stateBeanType, state);

                }

                callback.onSuccess(stateBean.as());
            }

            @Override
            public void onFailure(Throwable reason) {
                callback.onFailure(reason);
            }
        });
    }

    public <T> void set(String stateId, T state) {

        AutoBean<T> autoBean = AutoBeanUtils.<T, T> getAutoBean(state);

        String value = AutoBeanCodex.encode(autoBean).getPayload();

        stateStore.set(stateId, value);
    }
}
